﻿
using Levfier.Proccessors.Chains;
using Levfier.Proccessors.Exceptions;
using Levfier.Proccessors.Parameters;
using System;
using System.Collections.Generic;

namespace Levfier.Proccessors
{
    /// <summary>
    /// プロセッサ
    /// 入力音を処理し出力する
    /// </summary>
    [Serializable]
    abstract public class Proccessor : IProccessable
    {
        /// <summary>
        /// 後段のストリーム
        /// </summary>
        protected IChainable lowerChain;

        /// <summary>
        /// 上流チェインの接続数
        /// </summary>
        protected int upperConnectNum;

        /// <summary>
        /// パラメータのリスト
        /// key   : string
        /// value : ProccessParameter
        /// </summary>
        protected IDictionary<string, ProccessParameter> parameters;

        /// <summary>
        /// このプロセッサの有効/無効
        /// </summary>
        protected ProccessParameter bypassParam;

        /// <summary>
        /// バイパスフラグ
        /// </summary>
        protected bool bypass;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Proccessor()
        {
            try
            {
                this.parameters = new Dictionary<string, ProccessParameter>();

                //バイパスパラメータ
                this.bypassParam = new ProccessParameter();
                this.bypassParam.Type = ParameterType.Bool;
                this.bypassParam.Name = "Bypass";
                this.bypassParam.Value = 0.0d;
                this.bypassParam.MaxValue = 1.0d;
                this.bypassParam.MinValue = 0.0d;
                this.bypassParam.Step = 1.0d;
                this.bypassParam.ValueChanged += BypassParam_ValueChanged;
                this.parameters.Add(this.bypassParam.Name, this.bypassParam);

                this.InitParameters();
                this.upperConnectNum = 0;

                this.bypass = false;
            }
            catch(ArgumentException e)
            {
                throw new ProccessInitException("プロセッサの初期化に失敗しました。", e);
            }
        }

        /// <summary>
        /// 入力された音を処理する
        /// テンプレートメソッドパターン
        /// </summary>
        /// <param name="input">入力時系列データ</param>
        public virtual void Proccess(double[] input)
        {
            //後段のストリームが無い場合、処理しない
            if (null == this.lowerChain)
            {
                return;
            }

            if (this.bypass)
            {
                //バイパス処理
                this.ProccessBypass(input, this.lowerChain.GetBuffer());
            }
            else
            {
                //処理する
                this.InnerProccess(input, this.lowerChain.GetBuffer());
            }

            //後段のストリームへ流す
            lowerChain.FlowSounds();
        }

        /// <summary>
        /// 後段のストリームを設定する
        /// </summary>
        /// <param name="stream">後段のストリーム</param>
        /// <returns>常にtrue</returns>
        public void SetLowerChain(IChainable stream)
        {
            this.lowerChain = stream;
        }

        /// <summary>
        /// 後段のストリームを削除する
        /// </summary>
        /// <returns>常にtrue</returns>
        public void RemoveLowerChain()
        {
            this.lowerChain = null;
        }

        /// <summary>
        /// パラメータのハッシュテーブルを返す
        /// </summary>
        /// <returns>パラメータのハッシュテーブル</returns>
        public IDictionary<string, ProccessParameter> GetParameters()
        {
            return this.parameters;
        }

        /// <summary>
        /// 上流チェインから接続された際の動作
        /// </summary>
        /// <param name="chain">上流チェイン</param>
        public void NotifyConnect(IChainable chain)
        {
            this.upperConnectNum++;
        }

        /// <summary>
        /// 上流チェインから切断された際の動作
        /// </summary>
        /// <param name="chain">上流チェイン</param>
        public void NotifyDisconnect(IChainable chain)
        {
            this.upperConnectNum--;
        }

        /// <summary>
        /// 音の処理を行う
        /// 子クラスで実装すること
        /// </summary>
        /// <param name="input">入力時系列データ</param>
        /// <param name="output">出力時系列データ</param>
        abstract protected void InnerProccess(double[] inputBuffer, double[] outputBuffer);

        /// <summary>
        /// パラメータの初期化を行う
        /// 子クラスで実装すること
        /// </summary>
        abstract protected void InitParameters();

        /// <summary>
        /// 入力をそのまま出力する
        /// </summary>
        /// <param name="input">入力時系列データ</param>
        /// <param name="output">出力時系列データ</param>
        protected void ProccessBypass(double[] inputBuffer, double[] outputBuffer)
        {
            //計算
            for (int i = 0; i < inputBuffer.Length; i++)
            {
                outputBuffer[i] = inputBuffer[i];
            }
        }

        /// <summary>
        /// バイパスパラメータ変更時の処理
        /// </summary>
        /// <param name="sender"></param>
        private void BypassParam_ValueChanged(object sender)
        {
            this.bypass = (1.0d == this.bypassParam.Value);
        }
    }
}
